package org.jacky.javafx.pagination;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;
import javafx.scene.text.Font;
import javafx.util.StringConverter;
import javafx.util.converter.IntegerStringConverter;

import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.function.UnaryOperator;

public class PaginationComponent extends HBox implements Initializable {

    @FXML
    private Button pagination_first_page_btn;

    @FXML
    private Label pagination_first_page_space;

    @FXML
    private Button pagination_goto_btn;

    @FXML
    private Label pagination_goto_space;

    @FXML
    private TextField pagination_goto_txt;

    @FXML
    private Label pagination_goto_txt_space;

    @FXML
    private HBox pagination_home_hbox;

    @FXML
    private Button pagination_last_page_btn;

    @FXML
    private Label pagination_last_page_space;

    @FXML
    private Button pagination_next_page_btn;

    @FXML
    private Label pagination_next_page_space;

    @FXML
    private HBox pagination_page_list;

    @FXML
    private Button pagination_pre_page_btn;

    @FXML
    private Label pagination_pre_page_space;

    @FXML
    private Button pagination_total_btn;

    @FXML
    private Label pagination_total_page;

    @FXML
    private Label pagination_total_space;

    @FXML
    private ComboBox<PageSize> combo_box_page_size;

    private int pageSize = Constants.DEFAULT_PAGE_SIZE;
    private int totalSize = Constants.DEFAULT_TOTAL_SIZE;
    private int pageCount = Constants.DEFAULT_PAGE_COUNT;
    private int pageBtnCount = Constants.DEFAULT_BTN_COUNT;
    private int currentPage = Constants.DEFAULT_CURRENT_PAGE;
    private int fontSize = Constants.DEFAULT_FONT_SIZE;
    private static final List<PageSize> DEFAULT_CCB_PAGE_SIZE = new ArrayList<>();
    private static List<PageSize> pageSizeList = new ArrayList<>();
    private PageSize default_page;


    static {
        DEFAULT_CCB_PAGE_SIZE.add(new PageSize(10, "10/页"));
        DEFAULT_CCB_PAGE_SIZE.add(new PageSize(20, "20/页"));
        DEFAULT_CCB_PAGE_SIZE.add(new PageSize(30, "30/页"));
        DEFAULT_CCB_PAGE_SIZE.add(new PageSize(50, "50/页"));
        DEFAULT_CCB_PAGE_SIZE.add(new PageSize(100, "100/页"));
    }

    /**
     * ***************** 提供给外部调用的方法 ****************
     **/
    private ObjectProperty<EventHandler<ActionEvent>> onAction = new ObjectPropertyBase<EventHandler<ActionEvent>>() {

        @Override
        protected void invalidated() {
            PaginationComponent.this.setEventHandler(ActionEvent.ACTION, (EventHandler<? super ActionEvent>) this.get());
        }

        @Override
        public Object getBean() {
            return PaginationComponent.this;
        }

        @Override
        public String getName() {
            return "onAction";
        }
    };

    public final void setOnAction(EventHandler<ActionEvent> value) {
        this.onActionProperty().set(value);
    }

    public final ObjectProperty<EventHandler<ActionEvent>> onActionProperty() {
        return this.onAction;
    }

    public void setPaginationGotoVisible(boolean value) {
        this.pagination_goto_btn.setVisible(value);
        this.pagination_goto_space.setVisible(value);
        this.pagination_goto_txt.setVisible(value);
        this.pagination_goto_txt_space.setVisible(value);
        this.pagination_total_page.setVisible(value);
    }

    public void setPaginationTotalVisible(boolean value) {
        this.pagination_total_btn.setVisible(value);
        this.pagination_total_space.setVisible(value);
    }

    public void setPaginationFirstPageButtonVisible(boolean value) {
        this.pagination_first_page_btn.setVisible(value);
        this.pagination_first_page_space.setVisible(value);
    }

    public void setPaginationLastPageButtonVisible(boolean value) {
        this.pagination_last_page_btn.setVisible(value);
        this.pagination_last_page_space.setVisible(value);
    }

    public void setPaginationButtonFontSize(int fontSize) {
        this.fontSize = Math.max(2, fontSize);
        this.updatePagination();
    }

    /**
     * ***************** 以上是提供给外部调用的方法 ****************
     **/


    public PaginationComponent() {
        FXMLLoader loader = new FXMLLoader(this.getClass().getResource("/org/jacky/javafx/pagination/PaginationPicker.fxml"));
        loader.setRoot(this);
        loader.setController(this);
        try {
            loader.load();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        System.out.println("init.....");


        // 首页
        this.pagination_first_page_btn.setText("首 页");
        this.eventClickFirstPage(this.pagination_first_page_btn);
        // 上一页
        this.pagination_pre_page_btn.setText("上一页");
        this.eventClickPreviousPage(this.pagination_pre_page_btn);
        // 页列表
        this.pagination_page_list.getChildren().clear();

        // 下一页
        this.pagination_next_page_btn.setText("下一页");
        this.eventClickNextPage(this.pagination_next_page_btn);

        // 尾页
        this.pagination_last_page_btn.setText("尾 页");
        this.eventClickLastPage(this.pagination_last_page_btn);

        this.pagination_total_btn.setText("共0页");
        this.pagination_goto_btn.setText("跳转");
        this.eventClickGoto(this.pagination_goto_btn);

        this.pagination_goto_txt.setTextFormatter(new TextFormatter<>(new IntegerStringConverter(), null, this.digitsFilter()));
        this.pagination_total_page.setText("0");
        this.display(true);

        initPageSizeComboBox();
    }

    private void eventClickFirstPage(final Button btn) {
        this.eventButtonMouse(btn);
        btn.setOnAction(actionEvent -> {
            btn.setId(Constants.PAGINATION_BTN_CLICK);
            PaginationComponent.this.currentPage = PaginationComponent.this.totalSize > 0 ? 1 : 0;
            PaginationComponent.this.updatePagination();
        });
    }

    private void eventClickPreviousPage(final Button btn) {
        this.eventButtonMouse(btn);
        btn.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent actionEvent) {
                btn.setId(Constants.PAGINATION_BTN_CLICK);
                PaginationComponent.this.currentPage = PaginationComponent.this.totalSize > 0 ?
                        Math.max(PaginationComponent.this.currentPage - 1, 1) : 0;
                PaginationComponent.this.updatePagination();
            }
        });
    }

    private void eventClickNextPage(final Button btn) {
        this.eventButtonMouse(btn);
        btn.setOnAction(actionEvent -> {
            btn.setId(Constants.PAGINATION_BTN_CLICK);
            PaginationComponent.this.currentPage = PaginationComponent.this.totalSize > 0 ?
                    Math.min(PaginationComponent.this.currentPage + 1, PaginationComponent.this.pageCount) : 0;
            PaginationComponent.this.updatePagination();
        });
    }

    private void eventClickLastPage(final Button btn) {
        this.eventButtonMouse(btn);
        btn.setOnAction(actionEvent -> {
            btn.setId(Constants.PAGINATION_BTN_CLICK);
            int pageCount = PaginationComponent.this.totalSize / PaginationComponent.this.pageSize;
            pageCount += PaginationComponent.this.totalSize % PaginationComponent.this.pageSize == 0 ? 0 : 1;
            PaginationComponent.this.currentPage = pageCount;
            PaginationComponent.this.updatePagination();
        });
    }

    private void eventClickGoto(final Button btn) {
        this.eventButtonMouse(btn);
        btn.setOnAction(evt -> {
            btn.setId(Constants.PAGINATION_BTN_CLICK);
            int pageCount = PaginationComponent.this.totalSize / PaginationComponent.this.pageSize;
            pageCount += PaginationComponent.this.totalSize % PaginationComponent.this.pageSize == 0 ? 0 : 1;
            String gotoPage = PaginationComponent.this.pagination_goto_txt.getText();
            if (gotoPage != null && !"".equals(gotoPage) && gotoPage.length() != 0) {
                int newGogoPage = Math.max(pageCount > 0 ? 1 : 0, Integer.parseInt(gotoPage));
                PaginationComponent.this.currentPage = Math.min(pageCount, newGogoPage);
                PaginationComponent.this.updatePagination();
            } else {
                PaginationComponent.this.pagination_goto_txt.setText("");
            }
        });
    }

    private void eventButtonMouse(Button btn) {
        btn.setOnMouseEntered(evt -> btn.setId(Constants.PAGINATION_BTN_MOUSE_ENTERED));
        btn.setOnMouseExited(evt -> btn.setId(Constants.PAGINATION_BTN));
        btn.setOnAction(evt -> {
            String gotoPage = PaginationComponent.this.pagination_goto_txt.getText();
            if (gotoPage != null && gotoPage.length() == 0) {

            }
            int pageCount = PaginationComponent.this.totalSize / PaginationComponent.this.pageSize;
            pageCount += PaginationComponent.this.totalSize % PaginationComponent.this.pageSize == 0 ? 0 : 1;
            PaginationComponent.this.currentPage = pageCount;
            PaginationComponent.this.updatePagination();
        });
    }

    private void updatePagination() {
        if (this.totalSize != 0 || this.pagination_page_list.getChildren().size() != 0) {
            this.pagination_page_list.getChildren().clear();
            int start = 0;
            boolean middle = this.currentPage > (this.pageBtnCount + 1) / 2 && this.currentPage < this.pageCount - (this.pageBtnCount + 1) / 2;
//            int end = false;
            int i;
            Button button;
            Label label;
            if (!middle) {
                if (this.currentPage <= (this.pageBtnCount + 1) / 2) {
                    for (i = 0; i < this.pageCount; i++) {
                        button = new Button();
                        button.setUserData("page");
                        button.setId(Constants.PAGINATION_BTN);
                        this.eventPageButtonMouse(button);
                        label = new Label();
                        label.setUserData("label");
                        label.setId(Constants.PAGINATION_SPACE);
                        if (start == this.pageBtnCount - 1 && i + 1 != this.pageCount) {
                            button.setText("...");
                            button.setUserData("...>");
                            this.pagination_page_list.getChildren().add(button);
                            this.pagination_page_list.getChildren().add(label);
                        } else {
                            if (start == this.pageBtnCount) {
                                button.setText(String.valueOf(this.pageCount));
                                this.pagination_page_list.getChildren().add(button);
                                this.pagination_page_list.getChildren().add(label);
                                break;
                            }
                            button.setText(String.valueOf(i + 1));
                            this.pagination_page_list.getChildren().add(button);
                            this.pagination_page_list.getChildren().add(label);
                        }
                        ++start;
                    }
                } else if (this.currentPage >= this.pageCount - (this.pageBtnCount + 1) / 2) {
                    for (i = 0; i < this.pageCount; i++) {
                        button = new Button();
                        button.setUserData("page");
                        button.setId(Constants.PAGINATION_BTN);
                        this.eventPageButtonMouse(button);
                        label = new Label();
                        label.setUserData("label");
                        label.setId(Constants.PAGINATION_SPACE);

                        if (i == 0) {
                            button.setText(String.valueOf(1));
                            this.pagination_page_list.getChildren().add(button);
                            this.pagination_page_list.getChildren().add(label);
                        } else if (i == 1) {
                            button.setText("...");
                            button.setUserData("<...");
                            this.pagination_page_list.getChildren().add(button);
                            this.pagination_page_list.getChildren().add(label);
                        } else {
                            button.setText(String.valueOf(i + 1));
                            this.pagination_page_list.getChildren().add(button);
                            this.pagination_page_list.getChildren().add(label);
                        }

                        i = i == 1 ? this.pageCount - this.pageBtnCount : i;
                    }
                }
            } else {
                for (i = 1; i <= this.pageCount; i++) {
                    button = new Button();
                    button.setUserData("page");
                    button.setId(Constants.PAGINATION_BTN);
                    this.eventPageButtonMouse(button);
                    label = new Label();
                    label.setUserData("label");
                    label.setId(Constants.PAGINATION_SPACE);

                    if (i == 1) {
                        button.setText(String.valueOf(1));
                        this.pagination_page_list.getChildren().add(button);
                        this.pagination_page_list.getChildren().add(label);
                    } else if (i != 2 && i != this.pageCount - 1) {
                        button.setText(String.valueOf(i));
                        this.pagination_page_list.getChildren().add(button);
                        this.pagination_page_list.getChildren().add(label);
                    } else {
                        button.setText("...");
                        button.setUserData(i == 2 ? "<..." : "...>");
                        this.pagination_page_list.getChildren().add(button);
                        this.pagination_page_list.getChildren().add(label);
                        i = i == 2 ? this.currentPage - (this.pageBtnCount - 3) / 2 - 1 : i;
                    }
                    i = i == this.currentPage + (this.pageBtnCount - 3) / 2 ? this.pageCount - 2 : i;
                }
            }
        }

        this.pagination_goto_txt.setText(String.valueOf(this.currentPage));
        this.setPaginationBtnFontSize();
        this.pagination_page_list.getChildren().forEach(c -> {
            String val = String.valueOf(c.getUserData());
            if (val.contains("page")) {
                Button btn = (Button) c;
                int page = Integer.parseInt(btn.getText());
                c.setId(this.currentPage == page ? Constants.PAGINATION_BTN_CLICK : Constants.PAGINATION_BTN);
            }
        });
    }


    private void eventPageButtonMouse(final Button btn) {
        btn.setOnMouseEntered(evt -> {
            if (btn.getUserData().toString().contains("...") || this.currentPage != Integer.parseInt(btn.getText())) {
                btn.setId(Constants.PAGINATION_BTN_MOUSE_ENTERED);
            }
        });

        btn.setOnMouseExited(evt -> {
            if (btn.getUserData().toString().contains("...") || this.currentPage != Integer.parseInt(btn.getText())) {
                btn.setId(Constants.PAGINATION_BTN);
            }
        });

        btn.setOnAction(evt -> {
            String page = btn.getText();
            switch (btn.getUserData().toString()) {
                case "<...":
                    PaginationComponent.this.currentPage = Math.max(PaginationComponent.this.currentPage
                            - PaginationComponent.this.pageBtnCount - 2, 1);
                    break;
                case "...>":
                    PaginationComponent.this.currentPage = Math.min(PaginationComponent.this.currentPage +
                            PaginationComponent.this.pageBtnCount - 2, PaginationComponent.this.pageCount);
                case "label":
                default:
                    break;
                case "page":
                    if (PaginationComponent.this.currentPage == Integer.parseInt(page)) {
                        return;
                    }
                    PaginationComponent.this.currentPage = Integer.parseInt(page);
            }
            PaginationComponent.this.updatePagination();
        });
    }

    private void setPaginationBtnFontSize() {
        this.pagination_first_page_btn.setFont(new Font("Arial", this.fontSize));
        this.pagination_pre_page_btn.setFont(new Font("Arial", this.fontSize));
        this.pagination_page_list.getChildren().stream().filter(c -> !"label".equals(c.getUserData()))
                .forEach(btn -> {
                    Button b = (Button) btn;
                    b.setFont(new Font("Arial", this.fontSize));
                });
        this.pagination_next_page_btn.setFont(new Font("Arial", this.fontSize));
        this.pagination_last_page_btn.setFont(new Font("Arial", this.fontSize));
        this.pagination_total_btn.setFont(new Font("Arial", this.fontSize));
        this.pagination_goto_btn.setFont(new Font("Arial", this.fontSize));
        this.pagination_total_page.setFont(new Font("Arial", this.fontSize));
    }


    private UnaryOperator<TextFormatter.Change> digitsFilter() {
        return (change) -> {
            String input = change.getControlNewText();
            return input.matches("[0-9]*") ? change : null;
        };
    }

    private void display(boolean val) {
        this.pagination_first_page_btn.setDisable(val);
        this.pagination_pre_page_btn.setDisable(val);
        this.pagination_page_list.getChildren().forEach(c -> c.setDisable(val));
        this.pagination_next_page_btn.setDisable(val);
        this.pagination_last_page_btn.setDisable(val);
        this.pagination_total_btn.setDisable(val);
        this.pagination_goto_btn.setDisable(val);
        this.pagination_goto_txt.setDisable(val);
        this.pagination_total_page.setDisable(val);
    }


    public int getPageSize() {
        return pageSize;
    }

    public void setPageSize(Integer ps) {
        this.pageSize = ps != null && ps >= 1 ? ps : 10;
        this.calculatePageCount();
    }

    public int getTotalSize() {
        return totalSize;
    }

    public void setTotalSize(Integer total) {
        this.totalSize = total != null && total >= 0 ? total : 0;
        this.calculatePageCount();
    }

    public int getPageCount() {
        return pageCount;
    }


    public int getPageBtnCount() {
        return pageBtnCount;
    }

    public void setPageBtnCount(int ptc) {
        System.out.println("setPageBtnCount");
        this.pageCount = ptc >= 5 && ptc <= 21 ? (ptc % 2 > 0 ? ptc : ptc - 1) : 7;
        this.calculatePageCount();
    }

    public int getCurrentPage() {
        return currentPage;
    }

    public void setCurrentPage(Integer cp) {
        this.currentPage = cp < 1 ? (this.totalSize == 0 ? 0 : 1) : (currentPage > this.pageCount ? 1 : currentPage);
        this.calculatePageCount();
    }

    public int getFontSize() {
        return fontSize;
    }

    public void setFontSize(int fontSize) {
        this.fontSize = fontSize;
    }

    public void setComboBoxPageSize(List<PageSize> ccbPageSize) {
        if (Objects.isNull(ccbPageSize) || ccbPageSize.isEmpty()) {
            this.combo_box_page_size.getItems().addAll(DEFAULT_CCB_PAGE_SIZE);
        } else {
            this.combo_box_page_size.getItems().addAll(ccbPageSize);
            pageSizeList = ccbPageSize;
        }
        initPageSizeComboBox();
    }

    private void calculatePageCount() {
        this.pageCount = this.totalSize / this.pageSize;
        this.pageCount += this.totalSize % this.pageSize > 0 ? 1 : 0;
        this.pagination_total_page.setText(String.valueOf(this.pageCount));
        this.pagination_page_list.getChildren().clear();
        if (this.totalSize <= 0) {
            this.display(true);
            this.currentPage = 0;
        } else {
            this.display(false);
            this.currentPage = this.currentPage >= 1 && this.currentPage <= this.pageCount ? this.currentPage : 1;
            this.pagination_total_btn.setText("共" + this.totalSize + "条");
            this.updatePagination();
        }
    }

    private void initPageSizeComboBox() {
        if (pageSizeList == null || pageSizeList.isEmpty()) {
            pageSizeList = DEFAULT_CCB_PAGE_SIZE;
            default_page = pageSizeList.get(0);
        }
        this.combo_box_page_size.getItems().addAll(DEFAULT_CCB_PAGE_SIZE);
        combo_box_page_size.setValue(default_page);
        combo_box_page_size.setConverter(new StringConverter<PageSize>() {
            @Override
            public String toString(PageSize pageSize) {
                return pageSize.getLabel();
            }

            @Override
            public PageSize fromString(String s) {
                Optional<PageSize> first = pageSizeList.stream().filter(ps -> ps.getLabel().equals(s)).findFirst();
                return first.orElseGet(() -> pageSizeList.get(0));
            }
        });

        combo_box_page_size.getSelectionModel().selectedItemProperty().addListener((observable, oldVal, newVal) -> {
            pageSize = newVal.getPageSize();
            System.out.println("---- new page size:" + pageSize);
            this.calculatePageCount();
        });
    }
}
